package com.gitee.wsl.io

import com.gitee.wsl.ext.base.and
import com.gitee.wsl.ext.number.fromTwosComplementByteArray
import kotlinx.io.Buffer
import kotlinx.io.EOFException
import kotlinx.io.IOException
import kotlinx.io.InternalIoApi
import kotlinx.io.Sink
import kotlinx.io.Source
import kotlinx.io.UnsafeIoApi
import kotlinx.io.readByteArray
import kotlinx.io.readCodePointValue
import kotlinx.io.readLineStrict
import kotlinx.io.readShortLe
import kotlinx.io.readString
import kotlinx.io.readTo
import kotlinx.io.unsafe.UnsafeBufferOperations
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.math.min


@OptIn(InternalIoApi::class)
val Source.remaining: Long
    get() = buffer.size

 val Source.endOfInput: Boolean
    get() = exhausted()

 fun Source.readAvailable(buffer: ByteArray, offset: Int = 0, length: Int = buffer.size - offset): Int {
    val result = readAtMostTo(buffer, offset, offset + length)
    return if (result == -1) 0 else result
}

val ByteReadPacketEmpty: Source = kotlinx.io.Buffer()

fun ByteReadPacket(
    array: ByteArray,
    offset: Int = 0,
    length: Int = array.size
): Source = kotlinx.io.Buffer().apply {
    write(array, startIndex = offset, endIndex = offset + length)
}

/**
 * Build a byte packet in [block] lambda. Creates a temporary builder and releases it in case of failure
 */
@OptIn(ExperimentalContracts::class)
 inline fun buildPacket(block: Sink.() -> Unit): Source {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    val builder = kotlinx.io.Buffer()
    block(builder)
    return builder
}

/**
 * Reads at most [max] characters decoding bytes with specified [charset]. Extra character bytes will remain unconsumed
 * @return a decoded string
 */
@OptIn(InternalIoApi::class)
 fun Source.readText( max: Int = Int.MAX_VALUE): String {

        if (max == Int.MAX_VALUE) return readString()
        val count = min(buffer.size, max.toLong())
        return readString(count)
}

/**
 * Read exactly [charactersCount] characters interpreting bytes in the specified [charset].
 */
 fun Source.readTextExactCharacters(charactersCount: Int): String {
    val s = readText(charactersCount)
    if (s.length < charactersCount) {
        prematureEndOfStreamToReadChars(charactersCount)
    }
    return s
}


@OptIn(InternalIoApi::class)
 fun Source.readAvailable(out: kotlinx.io.Buffer): Int {
    val result = buffer.size
    out.transferFrom(this)
    return result.toInt()
}

@OptIn(InternalIoApi::class)
 fun Source.copy(): Source = buffer.copy()

@OptIn(InternalIoApi::class)
 fun Source.readShortLittleEndian(): Short {
    return buffer.readShortLe()
}

@OptIn(InternalIoApi::class)
 fun Source.discard(count: Long = Long.MAX_VALUE): Long {
    request(count)
    val countToDiscard = minOf(count, remaining)
    buffer.skip(countToDiscard)
    return countToDiscard
}

@OptIn(InternalIoApi::class)
 fun Source.takeWhile(block: (kotlinx.io.Buffer) -> Boolean) {
    while (!exhausted() && block(buffer)) {
    }
}

 fun Source.readFully(out: ByteArray, offset: Int = 0, length: Int = out.size - offset) {
    readTo(out, offset, offset + length)
}

@OptIn(InternalIoApi::class)
 fun <T> Source.preview(function: (Source) -> T): T {
    return buffer.peek().use(function)
}

internal fun Source.readLeb128(): Int {
    var result = 0
    for (shift in 0 until 32 step 7) {
        val b = readByte() and 0xff
        result = result or ((b and 0x7f) shl shift)
        if (b and 0x80 == 0) return result
    }
    throw IOException("unexpected leb128 value")
}

internal fun Source.readSleb128(): Int {
    val value = readLeb128()
    return (value ushr 1) xor -(value and 0x1)
}

private fun prematureEndOfStreamToReadChars(charactersCount: Int): Nothing =
    throw EOFException("Not enough input bytes to read $charactersCount characters.")

private fun Source.readChar(): Int {
    if (this.exhausted()) {
        return IO.EOF
    }
    return this.readCodePointValue()
}

private fun Source.peekChar(): Int {
    return this.peek().readChar()
}


/**
 * Decodes whole content of this stream into a string using [charset]. Returns empty string if the source is exhausted.
 *
 * @param charset the [com.fleeksoft.charset.Charset] to use for string decoding.
 *
 * @throws IllegalStateException when the source is closed.
 * @throws com.fleeksoft.io.exception.IOException when some I/O error occurs.
 *
 */
@OptIn(InternalIoApi::class)
fun Source.readString(charset: Charset): String {
    var req = 1L
    while (request(req)) {
        req *= 2
    }
    return buffer.readStringImpl(buffer.size, charset)
}

/**
 * Decodes [byteCount] bytes of this stream into a string using [charset].
 *
 * @param byteCount the number of bytes to read from the source for decoding.
 * @param charset the [Charset] to use for string decoding.
 *
 * @throws com.fleeksoft.io.exception.EOFException when the source exhausted before [byteCount] bytes could be read from it.
 * @throws IllegalStateException when the source is closed.
 * @throws IllegalArgumentException if [byteCount] is negative or its value is greater than [Int.MAX_VALUE].
 * @throws com.fleeksoft.io.exception.IOException when some I/O error occurs.
 *
 */
@OptIn(InternalIoApi::class)
fun Source.readString(byteCount: Long, charset: Charset): String {
    require(byteCount)
    return buffer.readStringImpl(byteCount, charset)
}

fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE) = readLineStrict(limit)


/**
 * Directly moves the byte array to a buffer without copying. Thus, it keeps bytes managed by a Buffer accessible.
 * The bytes may be overwritten through the Buffer or even recycled to be used by another buffer.
 * Therefore, operating on these bytes after wrapping leads to undefined behaviour.
 */
@UnsafeIoApi
fun ByteArray.wrapInUnsafeSource(): Source = Buffer().apply {
    UnsafeBufferOperations.moveToTail(this, this@wrapInUnsafeSource)
}

/**
 * Directly moves the byte array to a buffer without copying. Thus, it keeps bytes managed by a Buffer accessible.
 * The bytes may be overwritten through the Buffer or even recycled to be used by another buffer.
 * Therefore, operating on these bytes after wrapping leads to undefined behaviour.
 * [startIndex] is inclusive, [endIndex] is exclusive.
 */
@UnsafeIoApi
internal fun wrapInUnsafeSource(bytes: ByteArray, startIndex: Int = 0, endIndex: Int = bytes.size) = Buffer().apply {
    require(startIndex in 0..endIndex) { "StartIndex bust be between 0 and $endIndex" }
    UnsafeBufferOperations.moveToTail(this, bytes, startIndex, endIndex)
}

@OptIn(UnsafeIoApi::class)
internal inline fun <reified T> ByteArray.throughBuffer(operation: (Source) -> T): T =
    operation(wrapInUnsafeSource())


/**
 * Consumes exactly [nBytes] from this source and interprets it as a signed [ULong].
 *
 * @throws IllegalArgumentException if too much or too little data is present
 */
@Throws(IllegalArgumentException::class)
fun Source.readTwosComplementULong(nBytes: Int): ULong = ULong.fromTwosComplementByteArray(readByteArray(nBytes))


/**
 * Consumes exactly [nBytes] from this source and interprets it as a [Long].
 *
 * @throws IllegalArgumentException if too much or too little data is present
 */
@Throws(IllegalArgumentException::class)
fun Source.readTwosComplementLong(nBytes: Int): Long = Long.fromTwosComplementByteArray(readByteArray(nBytes))


/**
 * Consumes exactly [nBytes] from this source and interprets it as a signed [Int]
 *
 * @throws IllegalArgumentException if too much or too little data is present
 */
@Throws(IllegalArgumentException::class)
fun Source.readTwosComplementInt(nBytes: Int): Int = Int.fromTwosComplementByteArray(readByteArray(nBytes))

/**
 * Consumes exactly [nBytes] remaining data from this source and interprets it as a [UInt]
 *
 * @throws IllegalArgumentException if no or too much data is present
 */
@Throws(IllegalArgumentException::class)
fun Source.readTwosComplementUInt(nBytes: Int): UInt = UInt.fromTwosComplementByteArray(readByteArray(nBytes))
